#!/usr/bin/env python
#=========================================================================
# isa-sim [options]
#=========================================================================
#
#  -h --help           Display this message
#
#  --impl              {fl,cl,rtl}
#  --bmark <dataset>   {vvadd-unopt,vvadd-opt,cksum}
#  --translate         Simulate translated and imported DUTs
#  --trace             Display line tracing
#  --limit             Set max number of cycles, default=100000
#  --delay             Add some delays
#
# Author : Shunning Jiang, Christopher Batten
# Date   : June 10, 2019

# Hack to add project root to python path

import argparse
import os
import sys

from pymtl3 import *
from pymtl3.stdlib.mem import MagicMemoryCL
from pymtl3.stdlib.test_utils import TestSinkCL, TestSrcCL

# Hack to add project root to python path
sim_dir = os.path.dirname( os.path.abspath( __file__ ) )
while sim_dir:
  if os.path.exists( sim_dir + os.path.sep + "pytest.ini" ):
    sys.path.insert(0,sim_dir)
    break
  sim_dir = os.path.dirname(sim_dir)

from examples.ex03_proc.NullXcel import NullXcelRTL
from examples.ex03_proc.ProcCL import ProcCL
from examples.ex03_proc.ProcFL import ProcFL
from examples.ex03_proc.ProcRTL import ProcRTL
from examples.ex03_proc.SparseMemoryImage import SparseMemoryImage
from examples.ex03_proc.tinyrv0_encoding import assemble
from examples.ex03_proc.ubmark.proc_ubmark_cksum_roll import ubmark_cksum_roll
from examples.ex03_proc.ubmark.proc_ubmark_vvadd_opt import ubmark_vvadd_opt
from examples.ex03_proc.ubmark.proc_ubmark_vvadd_unopt import ubmark_vvadd_unopt

from test.harness import TestHarness

#=========================================================================
# Command line processing
#=========================================================================

class ArgumentParserWithCustomError(argparse.ArgumentParser):
  def error( self, msg = "" ):
    if ( msg ): print("\n"+f" ERROR: {msg}")
    print("")
    file = open( sys.argv[0] )
    for ( lineno, line ) in enumerate( file ):
      if ( line[0] != '#' ): sys.exit(msg != "")
      if ( (lineno == 2) or (lineno >= 4) ): print(line[1:].rstrip("\n"))

def parse_cmdline():
  p = ArgumentParserWithCustomError( add_help=False )

  # Standard command line arguments

  p.add_argument( "-h", "--help", action="store_true" )

  # Additional commane line arguments for the simulator

  p.add_argument( "--trace", action="store_true" )
  p.add_argument( "--impl",  default="rtl", choices=["fl", "cl", "rtl"] )
  p.add_argument( "--translate", action="store_true" )
  p.add_argument( "--bmark", default="vvadd-unopt",
                             choices=["vvadd-unopt", "vvadd-opt", "cksum"] )
  p.add_argument( "--limit",   default=1000000, type=int )
  p.add_argument( "--delay",   action="store_true" )

  opts = p.parse_args()
  if opts.help: p.error()
  return opts

impl_dict = {
  "fl" : ProcFL,
  "cl" : ProcCL,
  "rtl": ProcRTL,
}

bmark_dict = {
  "vvadd-unopt": ubmark_vvadd_unopt,
  "vvadd-opt"  : ubmark_vvadd_opt,
  "cksum"      : ubmark_cksum_roll
}

#=========================================================================
# Main
#=========================================================================

def main():
  opts = parse_cmdline()

  # Check if there are any conflicts in the given options

  # --translate can only be used on RTL proc
  if opts.translate:
    assert opts.impl == "rtl", \
      "--translate option can only be used with RTL processor implementation!"

  # Assemble the test program

  mem_image = bmark_dict[ opts.bmark ].gen_mem_image()

  #-----------------------------------------------------------------------
  # Setup simulator
  #-----------------------------------------------------------------------

  # Create test harness and elaborate

  if opts.delay:
    model = TestHarness( impl_dict[ opts.impl ], NullXcelRTL, 0,
                        # src sink memstall memlat
                          3,  4,   0.5,     4 )
  else:
    model = TestHarness( impl_dict[ opts.impl ], NullXcelRTL, 0,
                        # src sink memstall memlat
                          0,  0,   0,       1 )

  # Apply translation pass and import pass if required

  if opts.translate:
    from pymtl3.passes.backends.yosys import YosysTranslationImportPass
    model.elaborate()
    model.proc.set_metadata( YosysTranslationImportPass.enable, True )
    model = YosysTranslationImportPass()( model )

  model.apply( DefaultPassGroup(print_line_trace=opts.trace) )

  # Load the program into the model

  model.load( mem_image )

  #-----------------------------------------------------------------------
  # Run the simulation
  #-----------------------------------------------------------------------

  commit_inst = 0

  model.sim_reset()

  limit = 10000

  while not model.done() and model.sim_cycle_count() < limit:
    model.sim_tick()
    commit_inst += int(model.commit_inst)

  assert model.sim_cycle_count() < limit

  # Verify the results of simulation

  print()
  passed = bmark_dict[ opts.bmark ].verify( model.mem.mem.mem )
  print()
  if not passed:
    exit(1)

  # Display stats

  print( "  total_num_cycles      = {}".format( model.sim_cycle_count() ) )
  print( "  total_committed_insts = {}".format( commit_inst ) )
  print( "  CPI                   = {:1.2f}".format( model.sim_cycle_count()/float(commit_inst) ) )
  print()

  exit(0)

main()
